/******************************************************************************* * Copyright (c) 2005, 2007 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.ui.internal.menus; import org.eclipse.core.commands.ParameterizedCommand; import org.eclipse.core.commands.common.CommandException; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.core.runtime.Status; import org.eclipse.jface.menus.IWidget; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.CoolBar; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import org.eclipse.swt.widgets.Widget; import org.eclipse.ui.IWorkbenchWindowPulldownDelegate; import org.eclipse.ui.IWorkbenchWindowPulldownDelegate2; import org.eclipse.ui.handlers.IHandlerService; import org.eclipse.ui.internal.WorkbenchPlugin; import org.eclipse.ui.services.IServiceLocator; /** * <p> * A proxy for a {@link IWorkbenchWindowPulldownDelegate} on a pulldown action * set action. This delays the class loading until the delegate is really asked * for information. Asking a proxy for anything (except disposing) will cause * the proxy to instantiate the proxied delegate. * </p> * <p> * This class is not intended for use outside of the * <code>org.eclipse.ui.workbench</code> plug-in. * </p> * */ final class PulldownDelegateWidgetProxy implements IWidget { /** * A wrapper for loading the menu that defends against possible exceptions * triggered outside of the workbench. */ private static final class MenuLoader implements ISafeRunnable { /** * The parent for the menu to be created. This value is * <code>null</code> if the parent is a menu. */ private final Control control; /** * The delegate from which to load the menu. */ private final IWorkbenchWindowPulldownDelegate delegate; /** * The loaded menu. This value is <code>null</code> if the load * failed, or if it hasn't been loaded yet. */ private Menu menu = null; /** * The parent for the menu to be created. This value is * <code>null</code> if the parent is a control. */ private final Menu parent; /** * Constructs a new instance of <code>MenuLoader</code> * * @param delegate * The delegate from which the menu will be loaded; this * value must not be <code>null</code>. * @param parent * The parent of the menu to be loaded; this value must not * be <code>null</code>. */ private MenuLoader(final IWorkbenchWindowPulldownDelegate delegate, final Control parent) { this.delegate = delegate; this.parent = null; this.control = parent; } /** * Constructs a new instance of <code>MenuLoader</code> * * @param delegate * The delegate from which the menu will be loaded; this * value must not be <code>null</code>. * @param parent * The parent of the menu to be loaded; this value must not * be <code>null</code>. */ private MenuLoader(final IWorkbenchWindowPulldownDelegate2 delegate, final Menu parent) { this.delegate = delegate; this.parent = parent; this.control = null; } /** * Returns the menu loaded, if any. * * @return the loaded menu, or <code>null</code> if none. */ private Menu getMenu() { return menu; } /** * @see ISafeRunnable#handleException(java.lang.Throwable) */ public void handleException(Throwable exception) { // Do nothing } /** * @see ISafeRunnable#run() */ public void run() throws Exception { if (parent == null) { menu = delegate.getMenu(control); } else { menu = ((IWorkbenchWindowPulldownDelegate2) delegate) .getMenu(parent); } } } /** * The command to execute when the pulldown delegate appears in a tool bar, * and the arrow is <em>not</em> clicked. This also carries a help context * identifier. This value must be <code>null</code>. */ private final ParameterizedCommand command; /** * The configuration element from which the delegate can be created. This * value will exist until the element is converted into a real class -- at * which point this value will be set to <code>null</code>. */ private IConfigurationElement configurationElement; /** * The real delegate. This value is <code>null</code> until the proxy is * forced to load the real delegate. At this point, the configuration * element is converted, nulled out, and this delegate gains a reference. */ private IWorkbenchWindowPulldownDelegate delegate = null; /** * The name of the configuration element attribute which contains the * information necessary to instantiate the real delegate. */ private final String delegateAttributeName; private final DisposeListener disposeListener = new DisposeListener() { public void widgetDisposed(DisposeEvent e) { if (e.widget == widget) { dispose(); widget = null; // TODO Is this necessary? // disposeOldImages(); } } }; /** * The service locator from which a handler service can be retrieved. This * is needed if the pulldown appears in the tool bar, and the drop-down * arrow is <em>not</em> clicked. This value must not be <code>null</code>. */ private final IServiceLocator locator; private final Listener selectionListener = new Listener() { public final void handleEvent(final Event event) { final Widget item = event.widget; if (item == null) { return; } final int style = item.getStyle(); if (((style & SWT.DROP_DOWN) != 0) && (event.detail == SWT.ARROW) && (item instanceof ToolItem)) { // Create the submenu. final ToolItem toolItem = (ToolItem) item; final ToolBar toolBar = toolItem.getParent(); if (loadDelegate() && (delegate instanceof IWorkbenchWindowPulldownDelegate2)) { final IWorkbenchWindowPulldownDelegate2 delegate2 = (IWorkbenchWindowPulldownDelegate2) delegate; final MenuLoader loader = new MenuLoader(delegate2, toolBar); SafeRunner.run(loader); final Menu subMenu = loader.getMenu(); if (subMenu != null) { // position the menu below the drop down item final Rectangle bounds = toolItem.getBounds(); final Point location = toolBar.toDisplay(new Point( bounds.x, bounds.y + bounds.height)); subMenu.setLocation(location); subMenu.setVisible(true); return; // we don't fire the command } } } final IHandlerService service = (IHandlerService) locator .getService(IHandlerService.class); try { service.executeCommand(command, event); } catch (final CommandException e) { /* * TODO There should be an API on IHandlerService that handles * the exceptions. */ } } }; /** * The widget created for this pulldown delegate. If this proxy has not been * asked to fill or it has been disposed, then this value is * <code>null</code>. */ private Widget widget = null; /** * Constructs a new instance of <code>PulldownDelegateWidgetProxy</code> * with all the information it needs to try to avoid loading until it is * needed. * * @param configurationElement * The configuration element from which the real class can be * loaded at run-time; must not be <code>null</code>. * @param delegateAttributeName * The name of the attibute or element containing the delegate; * must not be <code>null</code>. * @param command * The command to execute if this the pulldown is not shown; must * not be <code>null</code>. * @param locator * A service locator from which a handler service can be * retrieved; must not be <code>null</code>. */ public PulldownDelegateWidgetProxy( final IConfigurationElement configurationElement, final String delegateAttributeName, final ParameterizedCommand command, final IServiceLocator locator) { if (configurationElement == null) { throw new NullPointerException( "The configuration element backing a handler proxy cannot be null"); //$NON-NLS-1$ } if (delegateAttributeName == null) { throw new NullPointerException( "The attribute containing the handler class must be known"); //$NON-NLS-1$ } if (command == null) { throw new NullPointerException("The command cannot be null"); //$NON-NLS-1$ } this.configurationElement = configurationElement; this.delegateAttributeName = delegateAttributeName; this.command = command; this.locator = locator; } /** * Passes the dipose on to the proxied handler, if it has been loaded. */ public final void dispose() { if (delegate != null) { delegate.dispose(); } } public final void fill(final Composite parent) { // This does not need to be supported. } public final void fill(CoolBar parent, final int index) { // This does not need to be supported. } public final void fill(final Menu parent, final int index) { if ((widget != null) || (parent == null)) { return; } // Create the menu item. final MenuItem menuItem; if (index >= 0) { menuItem = new MenuItem(parent, SWT.CASCADE, index); } else { menuItem = new MenuItem(parent, SWT.CASCADE); } menuItem.setData(this); widget = menuItem; // Create the submenu. if (loadDelegate() && (delegate instanceof IWorkbenchWindowPulldownDelegate2)) { final IWorkbenchWindowPulldownDelegate2 delegate2 = (IWorkbenchWindowPulldownDelegate2) delegate; final MenuLoader loader = new MenuLoader(delegate2, parent); SafeRunner.run(loader); final Menu subMenu = loader.getMenu(); if (subMenu != null) { menuItem.setMenu(subMenu); } } menuItem.addDisposeListener(disposeListener); menuItem.addListener(SWT.Selection, selectionListener); // TODO Needs a way to be linked to a command. // if (action.getHelpListener() != null) // menuItem.addHelpListener(action.getHelpListener()); // TODO Needs a way of updating itself // update(null); } public final void fill(final ToolBar parent, final int index) { if ((widget != null) && (parent == null)) { return; } final ToolItem toolItem; if (index >= 0) { toolItem = new ToolItem(parent, SWT.DROP_DOWN, index); } else { toolItem = new ToolItem(parent, SWT.DROP_DOWN); } toolItem.setData(this); widget = toolItem; // Attach some listeners. toolItem.addDisposeListener(disposeListener); toolItem.addListener(SWT.Selection, selectionListener); // TODO Needs a way to be linked to a command. // toolItem.addListener(SWT.Selection, getToolItemListener()); // action.addPropertyChangeListener(propertyListener); // if (action != null) { // String commandId = action.getActionDefinitionId(); // ExternalActionManager.ICallback callback = ExternalActionManager // .getInstance().getCallback(); // // if ((callback != null) && (commandId != null)) { // callback.addPropertyChangeListener(commandId, // actionTextListener); // } // } // TODO Needs a way of updating itself // update(null); } /** * Loads the delegate, if possible. If the delegate is loaded, then the * member variables are updated accordingly. * * @return <code>true</code> if the delegate is now non-null; * <code>false</code> otherwise. */ private final boolean loadDelegate() { if (delegate == null) { // Load the handler. try { delegate = (IWorkbenchWindowPulldownDelegate) configurationElement .createExecutableExtension(delegateAttributeName); configurationElement = null; return true; } catch (final ClassCastException e) { final String message = "The proxied delegate was the wrong class"; //$NON-NLS-1$ final IStatus status = new Status(IStatus.ERROR, WorkbenchPlugin.PI_WORKBENCH, 0, message, e); WorkbenchPlugin.log(message, status); return false; } catch (final CoreException e) { final String message = "The proxied delegate for '" + configurationElement.getAttribute(delegateAttributeName) //$NON-NLS-1$ + "' could not be loaded"; //$NON-NLS-1$ IStatus status = new Status(IStatus.ERROR, WorkbenchPlugin.PI_WORKBENCH, 0, message, e); WorkbenchPlugin.log(message, status); return false; } } return true; } public final String toString() { if (delegate == null) { return configurationElement.getAttribute(delegateAttributeName); } return delegate.toString(); } }